#pragma once

#include "Container_t.h"

/* A template class (T - template parameter),
   that represents an array container (extends class container), which can hold pointers to T.
   Allows various operations on array container, such as:
   insert(), remove(), find() and more.
*/
template <class T> class Array_t : public Container_t<T>
{

public:

	//static data members.
	static const int initialCapacity = 4;
	static const int expandValue = 4;

private:

	//data members.
	T** dataArray;
	int capacity;

public:

	Array_t() {

		capacity = initialCapacity;
		dataArray = new T*[capacity];
	}

	//copy CTOR.
	Array_t(const Array_t<T>& arr) : Container_t(arr), capacity(arr.capacity) {

		dataArray = new T*[capacity];

		for (int i = 0; i < count(); i++) {
			dataArray[i] = arr[i];   //using [] operator of Array_t.
		}
	}

	virtual ~Array_t() {
		
		delete[] dataArray;
	}

	//assignment operator for array.
	virtual Array_t<T>& operator=(const Array_t<T>& arr) {

		if (this != &arr) {  //to forbid a=a.
			
			removeAll();
			
			Container_t<T>::operator=(arr);  //calling operator= of base class.

			capacity = arr.capacity;

			delete[] dataArray;
			dataArray = new T*[capacity];

			for(int i = 0; i < count(); i++) {
				dataArray[i] = arr[i];   //using [] operator of Array_t.
			}
		}
	
		return *this;
	}

	/* seacrhes for a value in array, 
	   and returns pointer to it, if found (null ptr. if not). */
	virtual T* find(const T& value) {

		for(int i = 0; i < count(); i++) {  //searching in array.
			if (*(dataArray[i]) == value) {
				return dataArray[i];
			}
		}

		return 0;  //not found - null ptr.
	}

	//inserts element at end of array.
	virtual void insert(T* elem) {
		
		if (expansionNeeded()) {
			expandArray();
		}

		dataArray[count()] = elem;
	
		numOfElements++;
	}

	//appends element after given index in array.
	virtual void append(T* elem, int index) {

		if (!indexInBounds(index)) {
			throw("Index out of bounds - array append operation.");
		}

		if (count() == 0) {
			throw("Append to an empty array is illegal.");
		}

		if (expansionNeeded()) {
			expandArray();
		}

		shiftRight(index+1);
	
		dataArray[index+1] = elem;
	
		numOfElements++;
	}

	//prepends element before given index in arry.
	virtual void prepend(T* elem, int index) {

		if (!indexInBounds(index)) {
			throw("Index out of bounds - array prepend operation.");
		}

		if (count() == 0) {
			throw("Prepend to an empty array is illegal.");
		}


		if (expansionNeeded()) {
			expandArray();
		}

		shiftRight(index);
	
		dataArray[index] = elem;
	
		numOfElements++;
	}

	/* removes element from array, given its value (if found).
	   returns pointer to it, if found (null ptr. if not). */
	virtual T* remove(const T& value) {
		
		T* ret = 0;
		
		for(int i = 0; i < count(); i++) {

			if (*(dataArray[i]) == value) {

				ret = dataArray[i];
				shiftLeft(i+1);
				numOfElements--;
				return ret;
			}
		}

		return 0;  //not found - null ptr.
	}

	/* removes element from array, given its value (if found).
	   afterwards, deletes it. */
	virtual void removeAndDelete(const T& value) {

		delete remove(value);  //if remove(value) == 0, delete does nothing.
	}
	
	//removes all elements from array.
	virtual void removeAll() {
		
		Container_t<T>::removeAll();  //sets numOfElements to 0.
	}

	//removes and deletes all elements from array.
	virtual void removeAndDeleteAll() {

		for(int i = 0; i < count(); i++) {
			delete dataArray[i];
		}

		Container_t<T>::removeAndDeleteAll();  //sets numOfElements to 0.
	}
	
	//subscript operator for array.
	T*& operator[](int ind) const {

		if (!indexInBounds(ind)) {
			throw("Array index out of bounds - subscript operator.");
		}

		return dataArray[ind];
	}

private:
	
	//checks whether we need to expand the array, on insertion.
	bool expansionNeeded() const {

		return (count() == capacity);
	}

	//expands array by expandValue and re-copies the elements.
	void expandArray() {

		T** oldArray = dataArray;

		capacity = capacity + expandValue;
		dataArray = new T*[capacity];

		for(int i = 0; i < count(); i++) {
			dataArray[i] = oldArray[i];
		}

		delete[] oldArray;
	}

	/* shifts dataArray elements, starting from startInd, one position to the right.
       pre-cond.: capacity is sufficient for shifting. */
	void shiftRight(int startInd) {

		for(int i = count()-1; i >= startInd; i--) {
			dataArray[i+1] = dataArray[i];
		}
	}

	/* shifts dataArray elements, starting from startInd, one position to the left.
       pre-cond.: startInd > 0. */
	void shiftLeft(int startInd) {

		for(int i = startInd; i < count(); i++) {
			dataArray[i-1] = dataArray[i];
		}
	}

};

